/*

DMX512 Receiver controls 24 open collector switches,

100 Hz Soft-PWM used to contol LEDs, relays etc.

ATmega16  @16MHz, 8 Bit PWM
ATmega164 @20MHz, 10 Bit PWM, non linear mapping

*/

// includes

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <string.h>
#include <stdlib.h>
#include "dmx-pwm24.h"
//#include "lcd.h"
#include <util/delay.h>

// eeprom variables

uint8_t ee_enc_delta EEMEM = 1;

// global variables

volatile uint8_t flag_dmx;                  // new data available
volatile uint8_t flag_100ms;                // itself
volatile int8_t  enc_last;                  // encoder state
volatile int8_t  enc_delta;                 // encoder position
volatile uint8_t pwm_cnt_max=1;             // count limit
volatile uint8_t pwm_sync;                  // update now possible

uint8_t  dmx_data[DMX_SIZE];
PWM_t    pwm_data[PWM_CHANNELS];
PWM_t    pwm_setting_tmp[PWM_CHANNELS+1];   // PWM data, sorted

pwm_data_t isr_data;
pwm_data_t main_data;                       // PWM data

pwm_data_t* volatile isr_ptr = &isr_data;
pwm_data_t* volatile main_ptr = &main_data;

// channel assignment, 32 bit, PORT AABBCCDD,
// PMW channel -> port pin, defined via macros

const uint32_t pwm_mask_init[32] PROGMEM = {
	PAT(&PORTB, 2), PAT(&PORTB, 1), PAT(&PORTB, 0), PAT(&PORTA, 1), // 0-3
	PAT(&PORTA, 0), PAT(&PORTB, 3), PAT(&PORTA, 4), PAT(&PORTA, 3), // 4-7
	PAT(&PORTA, 2), PAT(&PORTA, 7), PAT(&PORTA, 6), PAT(&PORTA, 5), // 8-11
	PAT(&PORTC, 5), PAT(&PORTC, 6), PAT(&PORTC, 7), PAT(&PORTC, 2), // 12-15
	PAT(&PORTC, 3), PAT(&PORTC, 4), PAT(&PORTD, 7), PAT(&PORTC, 0), // 16-19
	PAT(&PORTC, 1), PAT(&PORTD, 6), PAT(&PORTD, 5), PAT(&PORTD, 4), // 20-23
0, 0, 0, 0, 0, 0, 0, 0};   // virtual channels

// table for nonlinear PWM conversion
// used to achieve visual linear dimming

// PWM[Index] = PWM[1]*1,027^index

const uint16_t pwmtable[256] PROGMEM = {
	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
	2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4,
	4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6,
	6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 9,
	9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13,
	14, 14, 14, 15, 15, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20,
	21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 28, 28, 29, 30, 31, 32,
	32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 49,
	50, 51, 53, 54, 56, 57, 59, 61, 62, 64, 66, 68, 69, 71, 73, 75,
	77, 79, 82, 84, 86, 89, 91, 94, 96, 99, 102, 104, 107, 110, 113, 116,
	120, 123, 126, 130, 133, 137, 141, 145, 149, 153, 157, 161, 166, 170, 175, 180,
	185, 190, 195, 200, 206, 211, 217, 223, 229, 236, 242, 249, 256, 263, 270, 278,
	285, 293, 301, 309, 318, 327, 336, 345, 354, 364, 374, 385, 395, 406, 417, 429,
	441, 453, 465, 478, 491, 505, 519, 533, 548, 563, 578, 594, 610, 627, 644, 662,
680, 699, 719, 738, 759, 780, 801, 823, 846, 869, 893, 918, 943, 969, 996, 1023};


//   functions

// swap pointers

inline void swap_pointers(void) {
	pwm_data_t* tmp_ptr;

	tmp_ptr = isr_ptr;
	isr_ptr = main_ptr;
	main_ptr = tmp_ptr;
}

// PWM update, convert PWM settings into control data for ISR

void pwm_update(PWM_t *data) {
	uint8_t i, j, k;
	PWM_t min;
	uint32_t tmp, tmp1;

	// copy pwm settings
	// fill in masks
	// generate pulse mask for PWM start
	tmp=0;
	for(i=1; i<=PWM_CHANNELS; i++) {
		pwm_setting_tmp[i]= data[i-1];
		tmp1=pgm_read_dword(&pwm_mask_init[i-1]);
		if (pwm_setting_tmp[i]) tmp |= tmp1;
		main_ptr->mask[i] = ~tmp1;
	}
	main_ptr->mask[0]=tmp;

	// sort PWM settings, insert sort
	
	for(i=1; i<PWM_CHANNELS; i++) {
		min=PWM_STEPS;
		k=i;
		for(j=i; j<=PWM_CHANNELS; j++) {
			if (pwm_setting_tmp[j]<min) {
				k=j;                        // store index and PWM value
				min = pwm_setting_tmp[j];
			}
		}
		if (k!=i) {
			// swap calculated minimum with actual sorting position
			min = pwm_setting_tmp[k];
			pwm_setting_tmp[k] = pwm_setting_tmp[i];
			pwm_setting_tmp[i] = min;
			tmp = main_ptr->mask[k];
			main_ptr->mask[k] = main_ptr->mask[i];
			main_ptr->mask[i] = tmp;
		}
	}

	// unite equal PWM values, delete PWM-value 0 and MAX

	k=PWM_CHANNELS;             // PWM_CHANNELS data entries

	for (i=1; i<PWM_CHANNELS; i++) {
		if (pwm_setting_tmp[i]==0 || pwm_setting_tmp[i]==PWM_STEPS) {
			k--;
			pwm_setting_tmp[i]=0;                       // delete entry
		}
		else if (pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) {
			k--;
			pwm_setting_tmp[i]=0;                       // delete entry
			main_ptr->mask[i+1] &= main_ptr->mask[i];   // unite masks
		}
	}

	// special case, all channels zero

	if (pwm_setting_tmp[i]==0 || pwm_setting_tmp[i]==PWM_STEPS) {
		k--;
		pwm_setting_tmp[i]=0;                       // delete entry
	}

	// fill gaps, move data to array start

	for (i=1, j=1; j<=PWM_CHANNELS; j++) {  // read all channels
	if (pwm_setting_tmp[j]!=0) {        //
	if (i!=j) {                 // move entry to actual position
	pwm_setting_tmp[i] = pwm_setting_tmp[j];
	main_ptr->mask[i] = main_ptr->mask[j];
}
i++;
        }
    }

    // calculate time differences

    if (k==0) {        // Special case, all channels zero
        main_ptr->time[0]=(uint16_t)T_PWM*PWM_STEPS/2;
        main_ptr->time[1]=(uint16_t)T_PWM*PWM_STEPS/2;
        k=1;
    }
    else {
        for (i=k, min=PWM_STEPS; i>0; i--) {
            main_ptr->time[i]=(uint16_t)T_PWM*(min-pwm_setting_tmp[i]);
            min=pwm_setting_tmp[i];
        }
        main_ptr->time[0]=(uint16_t)T_PWM*min;
    }

    // wait for sync

    pwm_sync=0;             // Sync will be set in interrupt
    while(pwm_sync==0);

    // swap pointers
    cli();
    swap_pointers();
    pwm_cnt_max = k;
    sei();
}

// convert dmx data into nonlinear PWM data

void convert(PWM_t *output, uint8_t *input) {
    uint8_t i;

    for (i=0; i<DMX_SIZE; i++) {
        //output[i] = input[i]<<2;        // transform 8 Bit into 10 bit 
       // output[i] = pgm_read_word(&pwmtable[input[i]]);   // non linear PWM, 10 bit
	   output[i] = input[i];
    }
}
 
// main

int main(void) {

    char tmp_string[4];
    uint8_t key_old, key_new;
    uint16_t dmx_adr, i, j;

    // variables init

    // load encoder position == DMX address from EEPROM
    enc_delta = eeprom_read_byte(&ee_enc_delta);
    if (enc_delta < 0)  enc_delta = 0;
    if (enc_delta > 84) enc_delta = 84;

    // init

    DDRA  = 0xFF;       // outputs, channel 0..7
    PORTB = (1<<PB6);   // PB6 pull-up ON
    DDRB  = ~(1<<PB6);  // PB0-PB3 channel 20-23
    DDRC  = 0xFF;       // outputs, channel 8..15
    PORTD = 0x0F;       // pull-ups ON for PD0-PD3
                        // PD4-PD7 channel 16-20
    DDRD  = 0xF0;       // PD0-PD3 input, rest outputs

    // UART config
 
    UBRRH = UBRR_VAL >> 8;
    UBRRL = UBRR_VAL & 0xFF;

    // use Timer 1 OCRA1

    TCCR1B = 2;             // timer runs at 1/8 clock
    TIMSK |= (1<<OCIE1A);   // enable interrupt

    //  PWM init, trick for generating a minimum timer frequency ~ 500 Hz
    //  4 virtual PWM channels with constant settings

    pwm_data[24] = 50;
    pwm_data[25] = 100;
    pwm_data[26] = 150;
    pwm_data[27] = 200;

    //  rotary encoder init
 
//     enc_last = 0;
//     if( PHASE_A ) enc_last = 3;
//     if( PHASE_B ) enc_last ^= 1;          // convert gray to binary

    // SPI init

    SPCR = (1<<SPE) | (1<<MSTR);
    SPSR = (1<<SPI2X);

//     // LCD init
//     lcd_init();
//     lcd_setcursor(0,0);
//     lcd_string_P(PSTR("DMX LED Module"));
//     lcd_setcursor(0,1);
//     lcd_string_P(PSTR("BUILD "));
//     itoa(BUILD_NO, tmp_string, 10);
//     lcd_string(tmp_string);
// 
// #ifndef DEBUG
//     _delay_ms(3000);
// #endif
//     lcd_setcursor(0,1);
//     lcd_string_P(PSTR("DMX Address:    "));
// 
//     key_old = KEY_PRESSED;
//  
    sei();

    // power on self test

    // fast fade R
    for(j=0; j<256; j+= 2) {
        for (i=0; i<8; i++) {
            dmx_data[3*i]   =j;
            dmx_data[3*i+1] =0;
            dmx_data[3*i+2] =0;
        }
        convert(pwm_data, dmx_data);
        pwm_update(pwm_data);
        _delay_ms(10);
    }   
 
    // fast fade G
    for(j=0; j<256; j+= 2) {
        for (i=0; i<8; i++) {
            dmx_data[3*i]   =0;
            dmx_data[3*i+1] =j;
            dmx_data[3*i+2] =0;
        }
        convert(pwm_data, dmx_data);
        pwm_update(pwm_data);
        _delay_ms(10);
    }   
 
    // fast fade B
    for(j=0; j<256; j+= 2) {
        for (i=0; i<8; i++) {
            dmx_data[3*i]   =0;
            dmx_data[3*i+1] =0;
            dmx_data[3*i+2] =j;
        }
        convert(pwm_data, dmx_data);
        pwm_update(pwm_data);
        _delay_ms(10);
    }   

    // all off
    for (i=0; i<24; i++) {
            dmx_data[i] =0;
    }
    convert(pwm_data, dmx_data);
    pwm_update(pwm_data);
 
    // activate UART fpr DMX reception
    UCSRB = (1<<RXCIE) | (1<<RXEN);

// endless main loop

    while (1) {
        if (flag_dmx) {
            flag_dmx=0;
            convert(pwm_data, dmx_data);
            pwm_update(pwm_data);
        }
//         if (flag_100ms) {
//             flag_100ms=0;
// 
//             // refresh LCD
// 
//             dmx_adr = DMX_ADR_CALC;   
//             itoa(dmx_adr, tmp_string, 10);
//             lcd_setcursor(12,1);
//             if (dmx_adr<10) lcd_data(' ');
//             if (dmx_adr<100) lcd_data(' ');
//             lcd_string(tmp_string);
// 
//             // store DMX adress on key release
//             key_new = KEY_PRESSED;
//             if (!key_new && key_old) {  // key release
//                 // save encoder postion == DMX base address
//                 // clear lower 2 MSBs and set LSB to avoid flickering DMX address
//                 eeprom_write_byte(&ee_enc_delta, (uint8_t) ((enc_delta & ~0x03) | 1));
//             }
//             key_old = key_new;
//         }
    }
}

// UART RX complete interrupt

ISR(USART_RXC_vect) {
    uint8_t status, data;
    static int16_t index=512;
 
    // read UART data, also clears interrupt flag            
    status = UCSRA;
    data = UDR;

    // allow nested interupt for PWM to minimize jitter
    sei();

    if (status & (1<<FE)) {                 // frame error
        if (data==0) {                      // break -> DMX Reset
            index = -1-DMX_ADR_CALC;   
        }
    }
    else {                                  // normal reception
        if ((index >= 0) && (index < DMX_SIZE)) {
            dmx_data[index]=data;           // store data
        }
        if (index == (DMX_SIZE-1)) {
            flag_dmx=1;                     // trigger update
        }
    }
    index++;
}

// Timer 1 Output COMPARE A Interrupt

ISR(TIMER1_COMPA_vect) {

    static uint8_t pwm_cnt, slow_cnt;
    ut_t tmp;
    int8_t enc_new, enc_diff;           // encoder stuff

    OCR1A   += isr_ptr->time[pwm_cnt];
    tmp.d32  = isr_ptr->mask[pwm_cnt];

    if (pwm_cnt == 0) {                 // set ports at begin of PWM
        PORTD = tmp.ary[0] | 0x0F;      // PD0-3 no change
        PORTC = tmp.ary[1];
        PORTB = tmp.ary[2] | 0xF0;      // PB4-7 no change
        PORTA = tmp.ary[3];
        pwm_cnt++;
        slow_cnt++;
        if (slow_cnt == 10) {           // 100ms timer
            slow_cnt=0;
            flag_100ms=1;               // trigger lcd refresh
        }
    }
    else {                              // clear ports
        PORTD &= (tmp.ary[0] | 0x0F);       
        PORTC &= tmp.ary[1];
        PORTB &= tmp.ary[2] | 0xF0;
        PORTA &= tmp.ary[3];
                                
        if (pwm_cnt == pwm_cnt_max) {
            pwm_sync = 1;               // update now possible
            pwm_cnt=0;
        }
        else pwm_cnt++;
    }
    
    // decode rotary encoder, setup for DMX address
    // only active, when jogdail is pressed
    // DMX address stored in eeprom on release in main loop
//     if (KEY_PRESSED) {
//         enc_new = 0;
//         if( PHASE_A ) enc_new = 3;
//         if( PHASE_B ) enc_new ^= 1;             // convert gray to binary
//         enc_diff = enc_last - enc_new;          // difference last - new
//         if( enc_diff & 1 ){                     // bit 0 = value (1)
//             enc_last   = enc_new;               // store new as next last
//             enc_delta += (enc_diff & 2) - 1;    // bit 1 = direction (+/-)
//             if (enc_delta < 0)  enc_delta = 0;
//             if (enc_delta > 84) enc_delta = 84;
//         }     
//     }
}
